package sshftp

import (
	"fmt"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"log"
	"net"
	"sync"
	"time"
)

const (
	DEFAULT_CHUNK_SIZE        = 65536
	MIN_CHUNKS                = 10
	THROUGHPUT_SLEEP_INTERVAL = 100
	MIN_THROUGHPUT            = DEFAULT_CHUNK_SIZE * MIN_CHUNKS * (1000 / THROUGHPUT_SLEEP_INTERVAL)
)




var (
	maxThroughputChan  = make(chan bool, MIN_CHUNKS)
	maxThroughput      uint64
	maxThroughputMutex sync.Mutex
	//sshClients      map[string]*SSHClient
	sshClientsMutex sync.RWMutex
)

func New(config Config) (client *Client,err error) {
	client = &Client{config: config}
	err = client.connect()
	if err!=nil{
		return nil,err
	}

	return client,nil

}

func (this *Client) connect() (err error) {
	if this.sshClient !=nil{
		_, _, err = this.sshClient.SendRequest("keepalive", false, nil)
		if err==nil{
			return nil
		}
	}

	cfg, err := this.newCfg()
	if err!=nil{
		return err
	}

	var sshClient *ssh.Client

	if this.config.Timeout > 0 {
		connNet, err := net.DialTimeout("tcp",this.config.Server, time.Duration(this.config.Timeout)*time.Second)
		if err != nil {
			return  err
		}
		sc, chans, reqs, err := ssh.NewClientConn(connNet,this.config.Server, cfg)
		if err != nil {
			return err
		}
		sshClient = ssh.NewClient(sc, chans, reqs)

	} else {
		sshClient, err = ssh.Dial("tcp",this.config.Server, cfg)
		if err != nil {
			return
		}
	}
	//初始化sshclient连接
	this.sshClient = sshClient
	sftpClient, err := sftp.NewClient(sshClient)
	if err!=nil{
		return err
	}
	//初始化sshSession连接
	session,err := NewSession(sshClient,nil,0)
	if err!=nil{
		return err
	}
	this.sshSession = session

	//初始化sftpClient连接
	this.sftpClient = sftpClient

	log.Println("dial ssh success")
	return
}


//func (c *Client) maxThroughputControl() {
//	for {
//		if c.MaxDataThroughput > 0 && c.MaxDataThroughput < MIN_THROUGHPUT {
//			log.Panicf("Minimal throughput is %d Bps", MIN_THROUGHPUT)
//		}
//		maxThroughputMutex.Lock()
//		throughput := c.MaxDataThroughput
//		maxThroughputMutex.Unlock()
//		chunks := throughput / DEFAULT_CHUNK_SIZE * THROUGHPUT_SLEEP_INTERVAL / 1000
//		if chunks < MIN_CHUNKS {
//			chunks = MIN_CHUNKS
//		}
//		for i := uint64(0); i < chunks; i++ {
//			maxThroughputChan <- true
//		}
//		if throughput > 0 {
//			time.Sleep(THROUGHPUT_SLEEP_INTERVAL * time.Millisecond)
//		}
//	}
//}

func (this *Client) newCfg() (config *ssh.ClientConfig,err error) {

	if this.config.Password == "" && this.config.PrivateKey == "" {
		return nil,fmt.Errorf("password or private key available")
	}
	if this.config.Username == "" || this.config.Server == "" {
		return nil,fmt.Errorf("username or server is required parameter, not allow empyt!")
	}

	auth := ssh.Password(this.config.Password)
	if this.config.PrivateKey !=""{
		signer, err := ssh.ParsePrivateKey([]byte(this.config.PrivateKey))
		if err != nil {
			return nil,fmt.Errorf("ssh parse private key: %w", err)
		}
		auth = ssh.PublicKeys(signer)
	}
	config = &ssh.ClientConfig{
		User: this.config.Username,
		Auth: []ssh.AuthMethod{
			auth,
		},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {return nil},
		Timeout: this.config.Timeout,
		Config:ssh.Config{KeyExchanges: this.config.KeyExchanges},
	}
	return config,nil
}



//func (c *SSHClient) TransferData(target string, data []byte) (stdout, stderr string, err error) {
//	go c.maxThroughputControl()
//
//	if c.isConnected == false {
//		_, err = c.Connect()
//		if err != nil {
//			return
//		}
//	}
//	currentSession, err := NewSession(c.remoteConn, nil, 0)
//	if err != nil {
//		return
//	}
//	defer currentSession.Close()
//	cmd := "cat >'" + strings.Replace(target, "'", "'\\''", -1) + "'"
//	stdinPipe, err := currentSession.StdinPipe()
//	if err != nil {
//		return
//	}
//	var stdoutBuf bytes.Buffer
//	var stderrBuf bytes.Buffer
//	currentSession.Stdout = &stdoutBuf
//	currentSession.Stderr = &stderrBuf
//	err = currentSession.session.Start(cmd)
//	if err != nil {
//		return
//	}
//	for start, max := 0, len(data); start < max; start += DEFAULT_CHUNK_SIZE {
//		<-maxThroughputChan
//		end := start + DEFAULT_CHUNK_SIZE
//		if end > max {
//			end = max
//		}
//		_, err = stdinPipe.Write(data[start:end])
//		if err != nil {
//			return
//		}
//	}
//	err = stdinPipe.Close()
//	if err != nil {
//		return
//	}
//	err = currentSession.Wait()
//	stdout = stdoutBuf.String()
//	stderr = stderrBuf.String()
//	return
//}

// Close closes open connections.
func (c *Client) Close() {
	if c.sftpClient != nil {
		c.sftpClient.Close()
	}
	if c.sshClient != nil {
		c.sshClient.Close()
	}
}





